Has convertido tu modelo del dominio en una aplicación web plenamente funcional. Esta aplicación ya es bastante útil de por sí, aunque aún puedes hacerle muchas mejoras. Transformemos pues tu aplicación en algo más serio, y de paso, aprendamos algunas cosas interesantes sobre OpenXava.
Empezaremos por añadir algo de lógica de negocio a tus entidades para hacer de tu aplicación algo más que un simple gestor de base de datos.
Propiedades calculadas
Quizás la lógica de negocio más simple que puedes añadir a tu aplicación es una propiedad calculada. Las propiedades que has usado hasta ahora son persistentes, es decir, cada propiedad se almacena en una columna de una tabla de la base de datos. Una propiedad calculada es una propiedad que no almacena su valor en la base de datos, sino que se calcula cada vez que se accede a la propiedad. Observa la diferencia entre una propiedad persistente y una calculada:
// Propiedad persistenteprivateint cantidad;// Tiene un campo, por tanto es persistentepublicint getCantidad(){// Un getter para devolver el valor del camporeturn cantidad;}publicvoid setCantidad(int cantidad){// Cambia el valor del campothis.cantidad = cantidad;}// Propiedad calculadapublicint getImporte(){// No tiene campo, ni setter, solo un getterreturn cantidad * precio;// con un cálculo}
Las propiedades calculadas son reconocidas automáticamente por OpenXava. Puedes usarlas en vistas, listas tabulares o cualquier otra parte de tu código.
Vamos a usar propiedades calculadas para añadir el elemento “económico” a nuestra aplicación Facturacion. Porque, tenemos líneas de detalle, productos, cantidades. Pero, ¿qué pasa con el dinero?
Propiedad calculada simple
El primer paso será añadir una propiedad de importe a Detalle. Lo que queremos es que cuando el usuario elija un producto y teclea la cantidad el importe de la línea sea recalculado y mostrado al usuario:
Añadir esta funcionalidad a tu actual código es prácticamente añadir una propiedad calculada a Detalle. Simplemente añade el código siguiente a la clase Detalle:
@Stereotype("DINERO")
@Depends("producto.numero, cantidad")// Cuando usuario cambie producto o cantidadpublicBigDecimal getImporte(){// esta propiedad se recalculará y se redibujaráreturnnewBigDecimal(cantidad).multiply(producto.getPrecio());}
Es tan solo poner el cálculo en getImporte() y usar @Depends para indicar a OpenXava que la propiedad importe depende de producto.numero y cantidad, así cada vez que el usuario cambia alguno de estos valores la propiedad se recalculará.
Ahora has de añadir esta nueva propiedad a la lista de propiedades mostradas en la colección detalles de DocumentoComercial:
Nada más. Tan solo necesitas añadir el getter y modificar la lista de propiedades. Ahora puedes probar los módulos Factura y Pedido para ver la propiedad importe en acción.
Nota: Verifica que los productos tengan precio registrado.
Usar @DefaultValueCalculator
La forma en que calculamos el importe de la línea de detalle no es la mejor. Tiene, al menos, dos inconvenientes. El primero es que el usuario puede querer tener la posibilidad de cambiar el precio unitario. Y segundo, si el precio de un producto cambia los importes de todas las facturas cambian también, y esto no es bueno.
Para evitar estos inconvenientes lo mejor es almacenar el precio de cada producto en cada línea de detalle. Añadamos pues una propiedad persistente precioPorUnidad a la clase Detalle, y calculemos su valor desde precio de Producto usando un @DefaultValueCalculator. De tal forma que consigamos el efecto que puedes ver en la siguiente figura:
El primer paso es obviamente añadir la propiedad precioPorUnidad. Añade el siguiente código a la clase Detalle:
@DefaultValueCalculator(
value=CalculadorPrecioPorUnidad.class, // Esta clase calcula el valor inicial
properties=@PropertyValue(
name="numeroProducto", // La propiedad numeroProducto del calculador...
from="producto.numero")// ... se llena con el valor de producto.numero de la entidad)
@Stereotype("DINERO")privateBigDecimal precioPorUnidad;// Una propiedad persistente convencional...publicBigDecimal getPrecioPorUnidad(){// ... con sus getter y setterreturn precioPorUnidad == null?BigDecimal.ZERO : precioPorUnidad;// así nunca devuelve nulo}publicvoid setPrecioPorUnidad(BigDecimal precioPorUnidad){this.precioPorUnidad = precioPorUnidad;}
CalculadorPrecioPorUnidad contiene la lógica para calcular el valor inicial. Simplemente lee el precio del producto. Observa el código de este calculador:
packageorg.openxava.facturacion.calculadores;// En el paquete calculadoresimportorg.openxava.calculators.*;importorg.openxava.facturacion.modelo.*;importstaticorg.openxava.jpa.XPersistence.*;//Para usar getManager()publicclass CalculadorPrecioPorUnidad implements ICalculator {privateint numeroProducto;
@OverridepublicObject calculate()throwsException{
Producto producto = getManager()// getManager() de XPersistence
.find(Producto.class, numeroProducto);// Busca el productoreturn producto.getPrecio();// Retorna su precio}publicint getNumeroProducto(){return numeroProducto;}publicvoid setNumeroProducto(int numeroProducto){this.numeroProducto = numeroProducto;}}
De esta forma cuando el usuario escoge un producto el campo de precio unitario se rellena con el precio del producto, pero, dado que es una propiedad persistente, el usuario puede cambiar este valor. Y si en el futuro el precio del producto cambiara este precio unitario de la línea de detalle no cambiaría.
Esto implica que has de adaptar la propiedad calculada importe:
@Stereotype("DINERO")
@Depends("precioPorUnidad, cantidad")// precioPorUnidad en vez de producto.numeropublicBigDecimal getImporte(){returnnewBigDecimal(cantidad).multiply(getPrecioPorUnidad());// getPrecioPorUnidad() en vez de producto.getPrecio()}
Ahora getImporte() usa precioPorUnidad como fuente en lugar de producto.precio.
Finalmente, debemos editar la entidad DocumentoComercial y modificar la lista de propiedades de la colección para mostrar la nueva propiedad:
Prueba los módulos Pedido y Factura y podrás observar el nuevo comportamiento al añadir líneas de detalle.
Propiedades calculadas dependientes de una colección
También queremos añadir importes a Pedido y Factura. Tener IVA, importe base e importe total es indispensable. Para hacerlo solo necesitas añadir unas pocas propiedades calculadas. La siguiente figura muestra la interfaz de usuario para estas propiedades:
Empecemos con importeBase. El siguiente código muestra su implementación:
publicBigDecimal getImporteBase(){BigDecimal resultado = newBigDecimal("0.00");for(Detalle detalle : getDetalles()){// Iteramos por todas las líneas de detalle
resultado = resultado.add(detalle.getImporte());// Acumulamos el importe}return resultado;}
La implementación es simple, se trata de sumar los importes de todas las líneas.
La siguiente propiedad a añadir es porcentajeIVA que se usará para calcular el IVA. El siguiente código muestra su implementación:
@Digits(integer=2, fraction=0)// Para indicar su tamaño
@Required
privateBigDecimal porcentajeIVA;publicBigDecimal getPorcentajeIVA(){return porcentajeIVA == null?BigDecimal.ZERO : porcentajeIVA;// Así nunca es nulo}publicvoid setPorcentajeIVA(BigDecimal porcentajeIVA){this.porcentajeIVA = porcentajeIVA;}
Puedes ver como porcentajeIVA es una propiedad persistente convencional. En este caso usamos @Digits (una anotación del entorno de validación Hibernate Validator) como una alternativa a @Column para especificar el tamaño.
Continuaremos añadiendo la propiedad IVA. La puedes ver en el siguiente código:
Una vez más un cálculo simple.
Ahora que ya has escrito las propiedades para los importes de DocumentoComercial, tienes que modificar la vista por defecto para mostrar porcentajeIVA y la lista de propiedades de la colección detalles para mostrar las propiedades de total de DocumentoComercial. Veamos una primera aproximación:
Prueba el módulo Factura y observarás las nuevas propiedades calculadas, pero, si pruebas el módulo Pedido estas propiedades no se mostrarán y obtendrás una fea excepción en el log de Eclipse. Esto es así porque las propiedades de total para Pedido no han sido definidas, solo las hemos definido para Factura. Observa como definir propiedades de total para Pedido y Factura en el siguiente código:
Primero eliminamos detalles de DocumentoComercial y declaramos un método abstracto que nos permitirá obtener los detalles de las subclases de DocumentoComercial.
Ahora veamos el código de Factura y Pedido:
publicclass Factura extends DocumentoComercial {
@ElementCollection
@ListProperties("producto.numero, producto.descripcion, cantidad, precioPorUnidad, " +
"importe[factura.importeBase, factura.iva, factura.importeTotal]")privateCollection<Detalle> detalles;publicCollection<Detalle> getDetalles(){// Este método implementa el método abstracto de 'DocumentoComecial'return detalles;}publicvoid setDetalles(Collection<Detalle> detalles){this.detalles = detalles;}// El resto del código fuente
...
}
publicclass Pedido extends DocumentoComercial {
@ElementCollection
@ListProperties("producto.numero, producto.descripcion, cantidad, precioPorUnidad, " +
"importe[pedido.importeBase, pedido.iva, pedido.importeTotal]"// Entidad padre Pedido => [pedido.importeBase, ...])privateCollection<Detalle> detalles;publicCollection<Detalle> getDetalles(){// Este método implementa el método abstracto de 'DocumentoComecial'return detalles;}publicvoid setDetalles(Collection<Detalle> detalles){this.detalles = detalles;}// El resto del código fuente
...
}
El código fuente añadido a Factura y Pedido generará dos tablas FACTURA_DETALLES y PEDIDO_DETALLES respectivamente.
Nota: Elimina la tabla DOCUMENTOCOMERCIAL_DETALLES, aprendiste a hacerlo en la lección anterior.
Ahora puedes probar tu aplicación. Debería funcionar casi como en la figura del inicio de esta sección. “Casi” porque porcentajeIVA todavía no tiene un valor por defecto. Lo añadiremos en la siguiente sección.
Valor por defecto desde un archivo de propiedades
Es conveniente para el usuario tener el campo porcentajeIVA lleno por defecto con un valor adecuado. Puedes usar un calculador (@DefaultValueCalculator) que devuelva un valor fijo, en este caso cambiar el valor por defecto implica cambiar el código fuente. O puedes leer el valor por defecto de una base de datos (usando JPA desde tu calculador), en este caso cambiar el valor por defecto implica actualizar la base de datos.
Otra opción es tener estos valores de configuración en un archivo de propiedades, un archivo plano con pares clave=valor. En este caso cambiar el valor por defecto de porcentajeIVA es tan simple como editar un archivo plano con un editor de texto.
Implementemos la opción del archivo de propiedades. Crea un archivo llamado facturacion.properties en la carpeta Facturacion/properties con el siguiente contenido:
porcentajeIVADefecto=18
Aunque puedes usar la clase java.util.Properties de Java para leer este archivo preferimos usar una clase propia para leer estas propiedades. Vamos a llamar a esta clase PreferenciasFacturacion y la pondremos en un nuevo paquete llamado org.openxava.facturacion.util. Veamos el código:
packageorg.openxava.facturacion.util;// En el paquete 'util'importjava.io.*;importjava.math.*;importjava.util.*;importorg.apache.commons.logging.*;importorg.openxava.util.*;publicclass PreferenciasFacturacion {privatefinalstaticString ARCHIVO_PROPIEDADES="facturacion.properties";privatestatic Log log = LogFactory.getLog(PreferenciasFacturacion.class);privatestaticProperties propiedades;// Almacenamos las propiedades aquíprivatestaticProperties getPropiedades(){if(propiedades == null){// Usamos inicialización vaga
PropertiesReader reader = // PropertiesReader es una clase de OpenXavanew PropertiesReader(
PreferenciasFacturacion.class, ARCHIVO_PROPIEDADES);try{
propiedades = reader.get();}catch(IOException ex){
log.error(
XavaResources.getString(// Para leer un mensaje i18n"properties_file_error",
ARCHIVO_PROPIEDADES),
ex);
propiedades = newProperties();}}return propiedades;}publicstaticBigDecimal getPorcentajeIVADefecto(){// El único método públicoreturnnewBigDecimal(getPropiedades().getProperty("porcentajeIVADefecto"));}}
Como puedes ver PreferenciasFacturacion es una clase con un método estático, getPorcentajeIVADefecto(). La ventaja de usar esta clase en lugar de leer directamente del archivo de propiedades es que si cambias la forma en que se obtienen las preferencias, por ejemplo leyendo de una base de datos o de un directorio LDAP, solo has de cambiar esta clase en toda tu aplicación.
Puedes usar esta clase desde el calculador por defecto para la propiedad porcentajeIVA. Aquí tienes el código del calculador:
packageorg.openxava.facturacion.calculadores;// En el paquete 'calculadores'importorg.openxava.calculators.*;// Para usar 'ICalculator'importorg.openxava.facturacion.util.*;// Para usar 'PreferenciasFacturacion'publicclass CalculadorPorcentajeIVA implements ICalculator {
@OverridepublicObject calculate()throwsException{return PreferenciasFacturacion.getPorcentajeIVADefecto();}}
Como ves, simplemente devuelve porcentajeIVADefecto de PreferenciasFacturacion. Ahora, ya puedes usar este calculador en la definición de la propiedad porcentajeIVA en DocumentoComercial. Mira el código:
Con este código cuando el usuario pulsa para crear una nueva factura, el campo porcentajeIVA se rellenará con 18, o cualquier otro valor que hayas puesto en facturacion.properties.
Métodos de retollamadas JPA
Otra forma práctica de añadir lógica de negocio a tu modelo es mediante los métodos de retrollamada JPA. Un método de retrollamada se llama en un momento específico del ciclo de vida de la entidad como objeto persistente. Es decir, puedes especificar cierta lógica a ejecutar al grabar, leer, borrar o modificar una entidad.
En esta sección veremos algunas aplicaciones prácticas de los métodos de retrollamada JPA.
Cálculo de valor por defecto multiusuario
Hasta ahora estamos calculando el número para Factura y Pedido usando @DefaultValueCalculator. Éste calcula el valor por defecto en el momento que el usuario pulsa para crear una nueva Factura o Pedido. Por tanto, si varios usuarios pulsan en el botón “nuevo” al mismo tiempo todos ellos obtendrán el mismo número. Esto no es apto para aplicaciones multiusuario. La forma correcta de generar un número único es generándolo justo en el momento de grabar.
Vamos a implementar la generación del número usando métodos de retrollamada JPA. JPA permite marcar cualquier método de tu clase para ser ejecutado en cualquier momento de su ciclo de vida. Indicaremos que justo antes de grabar un DocumentoComercial calcule su número. De paso mejoraremos el cálculo para tener una numeración diferente para Pedido y Factura.
Edita la entidad DocumentoComercial y añade el método calcularNumero(). Veamos el código:
@PrePersist // // Ejecutado justo antes de grabar el objeto por primera vezpublicvoid calcularNumero(){Query query = XPersistence.getManager().createQuery("select max(f.numero) from " +
getClass().getSimpleName() + // De esta forma es válido para Factura y Pedido" f where f.anyo = :anyo");
query.setParameter("anyo", anyo);Integer ultimoNumero = (Integer) query.getSingleResult();this.numero = ultimoNumero == null?1 : ultimoNumero + 1;}
El código anterior es el mismo que el de CalculadorSiguienteNumeroParaAnyo pero usando getClass().getSimpleName() en lugar de DocumentoComercial. El método getSimpleName() devuelve el nombre de la clase sin paquete, es decir, precisamente el nombre de la entidad. Será "Pedido" para Pedido y "Factura"" para Factura. Así podremos obtener una numeración diferente para Factura y Pedido.
La especificación JPA establece que no puedes usar el API JPA dentro de un método de retrollamada. Por tanto, el método de arriba no es legal desde un punto de vista estricto. Pero, Hibernate (la implementación de JPA que OpenXava usa por defecto) te permite usarla en @PrePersist. Y dado que usar JPA es la forma más fácil de hacer este cálculo, nosotros lo usamos.
Ahora borra la clase CalculadorSiguienteNumeroParaAnyo de tu proyecto, y modifica la propiedad numero de DocumentoComercial para que no la use. Mira el siguiente código:
@Column(length = 6)// @DefaultValueCalculator(value=CalculadorSiguienteNumeroParaAnyo.class, // Quita esto// properties=@PropertyValue(name="anyo")// )
@ReadOnly // El usuario no puede modificar el valorprivateint numero;
Nota que, además de quitar @DefaultValueCalculator, hemos añadido la anotación @ReadOnly. Esto significa que el usuario no puede introducir ni modificar este número. Esta es la forma correcta de hacerlo ahora dado que el número es generado al grabar el objeto, por lo que el valor que tecleara el usuario sería sobrescrito siempre.
Prueba ahora el módulo de Factura o Pedido, verás como el número está vacío y no es editable, y cuando grabes el documento, el número se calcula y se actualiza en la interfaz de usuario.
Sincronizar propiedades persistentes y calculadas
La forma en que calculamos el IVA, el importe base y el importe total es natural y práctica. Usamos propiedades calculadas que calculan, usando Java puro, los valores cada vez que son llamadas.
Pero, las propiedades calculadas tienen algunos inconvenientes. Por ejemplo, si quieres hacer un proceso masivo o un informe de todas las facturas cuyo importe total esté entre ciertos rangos. En estos casos, si tienes una base de datos demasiado grande el proceso puede ser lentísimo, porque has de instanciar todas las facturas para calcular su importe total. Una solución para este problema es tener una propiedad persistente, por tanto una columna en la base de datos para el importe de la factura o pedido; así el rendimiento es bastante mayor.
En nuestro caso mantendremos nuestra actuales propiedades calculadas, pero vamos a añadir una nueva, llamada importe, que contendrá el mismo valor que importeTotal, pero importe será persistente con su correspondiente columna en la base de datos. Lo complicado aquí es mantener sincronizado el valor de la propiedad importe. Vamos a usar métodos de retrollamada JPA (y un truco más) en DocumentoComercial para conseguirlo.
El primer paso es añadir la propiedad importe a DocumentoComercial. Nada más fácil, puedes verlo en el siguiente código:
Cuando el usuario añade, modifica o elimina un detalle en la interface de usuario, el iva, importe base e importe total son recalculados con datos frescos instantáneamente, no obstante, para persistir estos cambios el usuario debe Guardar el DocumentoComercial. Para sincronizar importe con importeTotal la primera vez que registramos un documento comercial, nosotros ya sabemos que debemos usar @PrePersist, pero, resulta que JPA no permite marcar más de un método con la misma anotación, por lo tanto, vamos a reordenar nuestro código. Veamos:
// @PrePersist // Elimina esta anotaciónpublicvoid calcularNumero(){Query query = XPersistence.getManager().createQuery("select max(f.numero) from " +
getClass().getSimpleName() + // De esta forma es válido para Factura y Pedido" f where f.anyo = :anyo");
query.setParameter("anyo", anyo);Integer ultimoNumero = (Integer) query.getSingleResult();this.numero = ultimoNumero == null?1 : ultimoNumero + 1;}
@PrePersist // Ejecutado justo antes de grabar el objeto por primera vezprivatevoid preGrabar()throwsException{
calcularNumero();
recalcularImporte();}publicvoid recalcularImporte(){
setImporte(getImporteTotal());}
Básicamente, llamamos a recalcularImporte() cada vez que una entidad DocumentoComercial es registrada por primera vez en la base de datos. Pero, recalcularImporte() también debe ser ejecutado en la actualización de detalles. Una primera aproximación puede ser marcar recalcularImporte con @PreUpdate, pero este sería ejecutado solo cuando cambian las propiedades de DocumentoComercial, nunca cuando cambian detalles. Nosotros superaremos esto, ejecutando recalcularImporte() siempre que el usuario grabe un DocumentoComercial. Veamos el siguiente código:
@Version
privateInteger version;// Añadida propiedad 'version', sin getter, ni setter
@PreUpdate // Añadido '@PreUPdate'publicvoid recalcularImporte(){// Ejecutado justo antes de actualizar el objeto
setImporte(getImporteTotal());}
La propiedad version asegura que la retrollamada @PreUpdate sea ejecutada siempre que el usuario Salve un DocumentoComercial, porque esta propiedad siempre será actualizada al guardar.
Puedes probar los módulos Factura y Pedido con este código y verás que cuando una línea de detalle se añade, remueve o modifica, la columna importe en la base de datos es correctamente actualizada después de grabar, lista para ser usada en un proceso masivo.
Nota: Elimina la tabla DOCUMENTOCOMERCIAL para que se vuelva a generar incluyendo la columna "version".
Lógica desde la base de datos (@Formula)
Idealmente escribirás toda tu lógica de negocio en Java, dentro de tus entidades. Sin embargo, hay ocasiones que esto no es lo más conveniente. Imagina que tienes una propiedad calculada en DocumentoComercial, digamos beneficioEstimado, como la siguiente:
Si necesitas realizar un proceso con todas las facturas con un beneficioEstimado mayor de 1000, has de escribir algo parecido al siguiente código:
Query query = getManager().createQuery("from Factura");// Sin condición en la consultafor(Object o : query.getResultList()){// Itera por todos los objetos
Factura f = (Factura) o;if(f.getBeneficioestimado()// Pregunta a cada objeto
.compareTo(newBigDecimal("1000"))>0){
f.doSomething();}}
No puedes usar una condición en la consulta para discriminar por beneficioEstimado, porque beneficioEstimado no está en la base de datos, solo está en el objeto Java, por tanto tienes que instanciar cada objeto para preguntar por su beneficioEstimado A veces esto es una buena opción, pero si tienes una cantidad inmensa de facturas, y solo unas cuantas tienen el beneficioEstimado mayor de 1000, entonces el proceso será muy ineficiente. ¿Qué alternativa tenemos?
Nuestra alternativa es usar la anotación @Formula. @Formula es una extensión de Hibernate al JPA estándar, que te permite mapear tu propiedad contra un estamento SQL. Puedes definir beneficioEstimado con @Formula como muestra el siguiente código:
@org.hibernate.annotations.Formula("IMPORTE * 0.10")// El cálculo usando SQL
@Stereotype("DINERO")privateBigDecimal beneficioEstimado;// Un campo, como con las propiedades persistentespublicBigDecimal getBeneficioEstimado(){// Sólo el getter es necesarioreturn beneficioEstimado;}
Esto indica que cuando un DocumentoComercial se lea de la base de datos, el campo beneficioEstimado se rellenará con el cálculo de @Formula, un cálculo que por cierto hace la base de datos. Lo más útil de las propiedades @Formula es que puedes usarlas en las condiciones, por tanto puedes reescribir el anterior proceso como muestra el siguiente código:
Query query = getManager().createQuery("from Factura f where f.beneficioEstimado > :beneficioEstimado");// Podemos usar una condición
query.setParameter("beneficioEstimado", newBigDecimal(1000));for(Object o: query.getResultList()){// Iteramos solo por los objetos seleccionados
Factura f = (Factura) o;
f.doSomething();}
De esta forma pones el peso del cálculo de beneficioEstimado y la selección de los registros en el servidor de base de datos, y no el servidor Java.
Este hecho también tiene efecto en modo lista, porque el usuario no puede filtrar ni ordenar por propiedades calculadas, pero sí por propiedades con @Formula: @Formula es una buena opción para mejorar el rendimiento en algunos casos. De todas formas, generalmente es mejor usar propiedades calculadas y escribir así tu lógica en Java. La ventaja de las propiedades calculadas sobre @Formula es que tu código no es dependiente de la base de datos. Además, con las propiedades calculadas puedes reejecutar el cálculo sin tener que leer el objeto de la base de datos, por tanto puedes usar @Depends.
Pruebas JUnit
Antes de ir a la siguiente lección, vamos a escribir el código JUnit para ésta. Recuerda, el código no está terminado si no tiene pruebas JUnit. Puedes escribir las pruebas antes, durante o después del código principal. Pero siempre has de escribirlas.
El código de prueba mostrado aquí no es solo para darte un buen ejemplo, sino también para enseñarte maneras de probar diferentes casos en tu aplicación OpenXava.
Modificar la prueba existente
Crear una nueva prueba para cada nuevo caso parece una buena idea desde un punto de vista estructural, pero en la mayoría de los casos no es práctico, porque de esa forma tu código de prueba crecerá muy rápido, y con el tiempo, ejecutar todas las pruebas supondrá muchísimo tiempo.
El enfoque más pragmático es modificar el código de prueba existente para cubrir todos los nuevos casos que hemos desarrollado. Hagámoslo de esta forma.
En nuestro caso, todo el código de esta lección aplica a DocumentoComercial, por tanto vamos a modificar el método testCrear() de PruebaDocumentoComercial para ajustarlo a la nueva funcionalidad. Dejamos el método testCrear() tal como muestra el siguiente código:
publicvoid testCrear()throwsException{
login("admin", "admin");
calcularNumero();// Añadido para calcular primero el siguiente número de documento
verificarValoresDefecto();
escogerCliente();
anyadirDetalles();
ponerOtrasPropiedades();
grabar();
verificarImporteYBeneficioEstimado();// Prueba el método de retrollamada y @Formula
verificarCreado();
borrar();}
Como ves, añadimos una nueva línea, después de login(...), para calcular el siguiente número de documento, y una llamada al nuevo método verificarImporteYBeneficioEstimado().
Ahora nos conviene más calcular el siguiente número de documento al principio para usarlo en el resto de la prueba. Para hacer esto, cambia el viejo método getNumero() por los dos métodos mostrados en el siguiente código:
privatevoid calcularNumero(){Query query = getManager().createQuery("select max(f.numero) from " +
modelo + // Cambiamos DocumentoComercial por una variable" f where f.anyo = :anyo");
query.setParameter("anyo", Dates.getYear(newDate()));Integer ultimoNumero = (Integer) query.getSingleResult();if(ultimoNumero == null) ultimoNumero = 0;
numero = Integer.toString(ultimoNumero + 1);}privateString getNumero(){return numero;}
Anteriormente, teníamos solo getNumero() que calculaba y devolvía el número, ahora tenemos un método para calcular (calcularNumero()), y otro para devolver el resultado (getNumero()). Puedes notar que la lógica del cálculo tiene un pequeño cambio, en vez de usar “DocumentoComercial” como fuente de la consulta usamos "modelo", una variable. Esto es así porque ahora la numeración para facturas y pedidos está separada. Llenamos esta variable, un campo de la clase de prueba, en el constructor, tal como muestra el siguiente código:
privateString modelo;// Nombre del modelo para la condición. Puede ser 'Factura' o 'Pedido'public PruebaDocumentoComercial(String nombrePrueba, String nombreModulo){super(nombrePrueba, "Facturacion", nombreModulo);this.modelo = nombreModulo;// El nombre del módulo coincide con el del modelo}
En este caso el nombre de módulo, "Factura" o "Pedido", coincide con el nombre de modelo, Factura o Pedido, así que la forma más fácil de obtener el nombre de modelo es desde el nombre de módulo.
Veamos el código que prueba la nueva funcionalidad.
Verificar valores por defecto y propiedades calculadas
En esta lección hemos hecho algunas modificaciones en los valores por defecto. Primero, el valor por defecto para numero ya no se calcula mediante un @DefaultValueCalculator en su lugar usamos un método de retrollamada JPA. Segundo, tenemos una nueva propiedad, porcentajeIVA, cuyo valor inicial se calcula leyendo de un archivo de propiedades. Para probar estos casos hemos de modificar el método verificarValoresDefecto() como ves en el siguiente código:
privatevoid verificarValoresDefecto()throwsException{
execute("CRUD.new");
assertValue("anyo", getAnyoActual());// assertValue("numero", getNumero()); // Ahora el número no tiene valor inicial...
assertValue("numero", "");// ... al crear un documento nuevo
assertValue("fecha", getFechaActual());
assertValue("porcentajeIVA", "18");// Valor de archivo de propiedades}
Comprobamos el cálculo del valor por defecto de procentajeIVA y verificamos que numero no tiene valor inicial, porque ahora numero no se calcula hasta el momento de grabar el documento (sección Cálculo de valor por defecto multiusuario). Cuando el documento (factura o pedido) se grabe verificaremos que numero se calcula. Cuando la línea se añade podemos verificar el cálculo de importe de detalle (la propiedad calculada simple, sección Propiedad calculada simple), el valor por defecto para precioPorUnidad (@DefaultValueCalculator, sección Usar @DefaultValueCalculator) y las propiedades de importes del documento (propiedades calculadas que dependen de una colección, sección Propiedades calculadas dependientes de una colección). Probamos todo esto haciendo unas ligeras modificaciones en el ya existente método anyadirDetalles():
privatevoid anyadirDetalles()throwsException{
assertCollectionRowCount("detalles", 0);// Añadir una línea de detalle
setValueInCollection("detalles", 0, "producto.numero", "1");
assertValueInCollection("detalles", 0,
"producto.descripcion", "Peopleware: Productive Projects and Teams");
assertValueInCollection("detalles", 0,
"precioPorUnidad", "31,00");// @DefaultValueCalculator, section 'Usar @DefaultValueCalculator'
setValueInCollection("detalles", 0, "cantidad", "2");
assertValueInCollection("detalles", 0,
"importe", "62,00");// Propiedada calculada, sección 'Propiedad calculada simple'// Verificando propiedades calculadas del documento
assertTotalInCollection("detalles", 0, "importe", "62,00");// Propiedades calculadas
assertTotalInCollection("detalles", 1, "importe", "11,16");// dependientes de una coleccion,
assertTotalInCollection("detalles", 2,
"importe", "73,16");// sección 'Propiedades calculadas dependientes de una colección'// Añadir otro detalle
setValueInCollection("detalles", 1, "producto.numero", "2");
assertValueInCollection("detalles", 1, "producto.descripcion", "Arco iris de lágrimas");
assertValueInCollection("detalles", 1,
"precioPorUnidad", "15,00");// @DefaultValueCalculator, sección 'Usar @DefaultValueCalculator'
setValueInCollection("detalles", 1, "precioPorUnidad", "10,00");// Modificando el valor pode defecto
setValueInCollection("detalles", 1, "cantidad", "1");
assertValueInCollection("detalles", 1, "importe", "10,00");
assertCollectionRowCount("detalles", 2);// Verificando propiedades calculadas del documento
assertTotalInCollection("detalles", 0, "importe", "72,00");
assertTotalInCollection("detalles", 1, "importe", "12,96");
assertTotalInCollection("detalles", 2, "importe", "84,96");}
Como ves, con estas modificaciones sencillas probamos la mayoría de nuestro nuevo código. Nos quedan sólo las propiedades importe y beneficioEstimado. Las cuales probaremos en la siguiente sección.
Sincronización entre propiedad persistente y calculada / @Formula
En la sección Sincronizar propiedades persistentes y calculadas usamos métodos de retrollamada de JPA en DocumentoComercial para tener una propiedad persistente, importe, sincronizada con una calculada, importeTotal. La propiedad importe solo se muestra en modo lista.
En la sección Lógica desde la base de datos hemos creado una propiedad que usa @Formula, beneficioEstimado. Esta propiedad se muestra solo en modo lista.
Obviamente, la forma más simple de probarlo es yendo a modo lista y verificando que los valores para estas dos propiedades son los esperados. En testCrear() llamamos a verificarImporteYBeneficioEstimado(). Veamos su código:
privatevoid verificarImporteYBeneficioEstimado()throwsException{
execute("Mode.list");// Cambiar a modo lista
setConditionValues(newString[]{// Filtra para ver en la lista solamente
getAnyoActual(), getNumero()// el documento que acabamos de crear});
execute("List.filter");// Hace filtro
assertValueInList(0, 0, getAnyoActual());// Verifica que
assertValueInList(0, 1, getNumero());// el filtro ha funcionado
assertValueInList(0, "importe", "84,96");// Confirma el importe
assertValueInList(0, "beneficioEstimado", "8,50");// Confirma el beneficio estimado
execute("Mode.detailAndFirst");// Va a modo detalle}
Dado que ahora vamos a modo lista y después volvemos a detalles, hemos de hacer una pequeña modificación en el método verificarCreado(), que es ejecutado justo después de verificarImporteYBeneficioEstimado(). Veamos la modificación:
privatevoid verificarCreado()throwsException{// setValue("anyo", getAnyoActual()); // Borramos estas líneas// setValue("numero", getNumero()); // para buscar el documento// execute("Search.search"); // porque ya lo hemos buscado desde el modo lista// El resto de la prueba ...
...
Quitamos estas líneas porque ahora no es necesario buscar el documento recién creado. Ahora en el método verificarImporteYBeneficioEstimado() vamos a modo lista y escogemos el documento, por tanto ya estamos editando el documento.
¡Enhorabuena! Ahora tus pruebas ya están sincronizadas con tu código. Es un buen momento para ejecutar todas las pruebas de tu aplicación.
Resumen
En esta lección has aprendido algunas formas comunes de añadir lógica de negocio a tus entidades. No hay duda sobre la utilidad de las propiedades calculadas, los métodos de retrollamada o @Formula. Sin embargo, todavía tenemos muchas otras formas de añadir lógica a tu aplicación OpenXava, que vamos a aprender a usar.
En futuros lecciones verás como añadir validación, modificar el funcionamiento estándar del módulo y añadir tu propia lógica de negocio, entre otras formas de añadir lógica personalizada a tu aplicación.
Table of Contents
Lección 5: Lógica de negocio básica
Has convertido tu modelo del dominio en una aplicación web plenamente funcional. Esta aplicación ya es bastante útil de por sí, aunque aún puedes hacerle muchas mejoras. Transformemos pues tu aplicación en algo más serio, y de paso, aprendamos algunas cosas interesantes sobre OpenXava.Empezaremos por añadir algo de lógica de negocio a tus entidades para hacer de tu aplicación algo más que un simple gestor de base de datos.
Propiedades calculadas
Quizás la lógica de negocio más simple que puedes añadir a tu aplicación es una propiedad calculada. Las propiedades que has usado hasta ahora son persistentes, es decir, cada propiedad se almacena en una columna de una tabla de la base de datos. Una propiedad calculada es una propiedad que no almacena su valor en la base de datos, sino que se calcula cada vez que se accede a la propiedad. Observa la diferencia entre una propiedad persistente y una calculada:Las propiedades calculadas son reconocidas automáticamente por OpenXava. Puedes usarlas en vistas, listas tabulares o cualquier otra parte de tu código.
Vamos a usar propiedades calculadas para añadir el elemento “económico” a nuestra aplicación Facturacion. Porque, tenemos líneas de detalle, productos, cantidades. Pero, ¿qué pasa con el dinero?
Propiedad calculada simple
El primer paso será añadir una propiedad de importe a Detalle. Lo que queremos es que cuando el usuario elija un producto y teclea la cantidad el importe de la línea sea recalculado y mostrado al usuario:Añadir esta funcionalidad a tu actual código es prácticamente añadir una propiedad calculada a Detalle. Simplemente añade el código siguiente a la clase Detalle:
Es tan solo poner el cálculo en getImporte() y usar @Depends para indicar a OpenXava que la propiedad importe depende de producto.numero y cantidad, así cada vez que el usuario cambia alguno de estos valores la propiedad se recalculará.
Ahora has de añadir esta nueva propiedad a la lista de propiedades mostradas en la colección detalles de DocumentoComercial:
Nada más. Tan solo necesitas añadir el getter y modificar la lista de propiedades. Ahora puedes probar los módulos Factura y Pedido para ver la propiedad importe en acción.
Verifica que los productos tengan precio registrado.
Usar @DefaultValueCalculator
La forma en que calculamos el importe de la línea de detalle no es la mejor. Tiene, al menos, dos inconvenientes. El primero es que el usuario puede querer tener la posibilidad de cambiar el precio unitario. Y segundo, si el precio de un producto cambia los importes de todas las facturas cambian también, y esto no es bueno.Para evitar estos inconvenientes lo mejor es almacenar el precio de cada producto en cada línea de detalle. Añadamos pues una propiedad persistente precioPorUnidad a la clase Detalle, y calculemos su valor desde precio de Producto usando un @DefaultValueCalculator. De tal forma que consigamos el efecto que puedes ver en la siguiente figura:
El primer paso es obviamente añadir la propiedad precioPorUnidad. Añade el siguiente código a la clase Detalle:
CalculadorPrecioPorUnidad contiene la lógica para calcular el valor inicial. Simplemente lee el precio del producto. Observa el código de este calculador:
De esta forma cuando el usuario escoge un producto el campo de precio unitario se rellena con el precio del producto, pero, dado que es una propiedad persistente, el usuario puede cambiar este valor. Y si en el futuro el precio del producto cambiara este precio unitario de la línea de detalle no cambiaría.
Esto implica que has de adaptar la propiedad calculada importe:
Ahora getImporte() usa precioPorUnidad como fuente en lugar de producto.precio.
Finalmente, debemos editar la entidad DocumentoComercial y modificar la lista de propiedades de la colección para mostrar la nueva propiedad:
Prueba los módulos Pedido y Factura y podrás observar el nuevo comportamiento al añadir líneas de detalle.
Propiedades calculadas dependientes de una colección
También queremos añadir importes a Pedido y Factura. Tener IVA, importe base e importe total es indispensable. Para hacerlo solo necesitas añadir unas pocas propiedades calculadas. La siguiente figura muestra la interfaz de usuario para estas propiedades:Empecemos con importeBase. El siguiente código muestra su implementación:
La implementación es simple, se trata de sumar los importes de todas las líneas.
La siguiente propiedad a añadir es porcentajeIVA que se usará para calcular el IVA. El siguiente código muestra su implementación:
Puedes ver como porcentajeIVA es una propiedad persistente convencional. En este caso usamos @Digits (una anotación del entorno de validación Hibernate Validator) como una alternativa a @Column para especificar el tamaño.
Continuaremos añadiendo la propiedad IVA. La puedes ver en el siguiente código:
Es un cálculo simple.
Solo nos queda importeTotal por añadir. Puedes ver su código:
Una vez más un cálculo simple.
Ahora que ya has escrito las propiedades para los importes de DocumentoComercial, tienes que modificar la vista por defecto para mostrar porcentajeIVA y la lista de propiedades de la colección detalles para mostrar las propiedades de total de DocumentoComercial. Veamos una primera aproximación:
Prueba el módulo Factura y observarás las nuevas propiedades calculadas, pero, si pruebas el módulo Pedido estas propiedades no se mostrarán y obtendrás una fea excepción en el log de Eclipse. Esto es así porque las propiedades de total para Pedido no han sido definidas, solo las hemos definido para Factura. Observa como definir propiedades de total para Pedido y Factura en el siguiente código:
Primero eliminamos detalles de DocumentoComercial y declaramos un método abstracto que nos permitirá obtener los detalles de las subclases de DocumentoComercial.
Ahora veamos el código de Factura y Pedido:
El código fuente añadido a Factura y Pedido generará dos tablas FACTURA_DETALLES y PEDIDO_DETALLES respectivamente.
Elimina la tabla DOCUMENTOCOMERCIAL_DETALLES, aprendiste a hacerlo en la lección anterior.
Valor por defecto desde un archivo de propiedades
Es conveniente para el usuario tener el campo porcentajeIVA lleno por defecto con un valor adecuado. Puedes usar un calculador (@DefaultValueCalculator) que devuelva un valor fijo, en este caso cambiar el valor por defecto implica cambiar el código fuente. O puedes leer el valor por defecto de una base de datos (usando JPA desde tu calculador), en este caso cambiar el valor por defecto implica actualizar la base de datos.Otra opción es tener estos valores de configuración en un archivo de propiedades, un archivo plano con pares clave=valor. En este caso cambiar el valor por defecto de porcentajeIVA es tan simple como editar un archivo plano con un editor de texto.
Implementemos la opción del archivo de propiedades. Crea un archivo llamado facturacion.properties en la carpeta Facturacion/properties con el siguiente contenido:
Aunque puedes usar la clase java.util.Properties de Java para leer este archivo preferimos usar una clase propia para leer estas propiedades. Vamos a llamar a esta clase PreferenciasFacturacion y la pondremos en un nuevo paquete llamado org.openxava.facturacion.util. Veamos el código:
Como puedes ver PreferenciasFacturacion es una clase con un método estático, getPorcentajeIVADefecto(). La ventaja de usar esta clase en lugar de leer directamente del archivo de propiedades es que si cambias la forma en que se obtienen las preferencias, por ejemplo leyendo de una base de datos o de un directorio LDAP, solo has de cambiar esta clase en toda tu aplicación.
Puedes usar esta clase desde el calculador por defecto para la propiedad porcentajeIVA. Aquí tienes el código del calculador:
Como ves, simplemente devuelve porcentajeIVADefecto de PreferenciasFacturacion. Ahora, ya puedes usar este calculador en la definición de la propiedad porcentajeIVA en DocumentoComercial. Mira el código:
Con este código cuando el usuario pulsa para crear una nueva factura, el campo porcentajeIVA se rellenará con 18, o cualquier otro valor que hayas puesto en facturacion.properties.
Métodos de retollamadas JPA
Otra forma práctica de añadir lógica de negocio a tu modelo es mediante los métodos de retrollamada JPA. Un método de retrollamada se llama en un momento específico del ciclo de vida de la entidad como objeto persistente. Es decir, puedes especificar cierta lógica a ejecutar al grabar, leer, borrar o modificar una entidad.En esta sección veremos algunas aplicaciones prácticas de los métodos de retrollamada JPA.
Cálculo de valor por defecto multiusuario
Hasta ahora estamos calculando el número para Factura y Pedido usando @DefaultValueCalculator. Éste calcula el valor por defecto en el momento que el usuario pulsa para crear una nueva Factura o Pedido. Por tanto, si varios usuarios pulsan en el botón “nuevo” al mismo tiempo todos ellos obtendrán el mismo número. Esto no es apto para aplicaciones multiusuario. La forma correcta de generar un número único es generándolo justo en el momento de grabar.Vamos a implementar la generación del número usando métodos de retrollamada JPA. JPA permite marcar cualquier método de tu clase para ser ejecutado en cualquier momento de su ciclo de vida. Indicaremos que justo antes de grabar un DocumentoComercial calcule su número. De paso mejoraremos el cálculo para tener una numeración diferente para Pedido y Factura.
Edita la entidad DocumentoComercial y añade el método calcularNumero(). Veamos el código:
El código anterior es el mismo que el de CalculadorSiguienteNumeroParaAnyo pero usando getClass().getSimpleName() en lugar de DocumentoComercial. El método getSimpleName() devuelve el nombre de la clase sin paquete, es decir, precisamente el nombre de la entidad. Será "Pedido" para Pedido y "Factura"" para Factura. Así podremos obtener una numeración diferente para Factura y Pedido.
La especificación JPA establece que no puedes usar el API JPA dentro de un método de retrollamada. Por tanto, el método de arriba no es legal desde un punto de vista estricto. Pero, Hibernate (la implementación de JPA que OpenXava usa por defecto) te permite usarla en @PrePersist. Y dado que usar JPA es la forma más fácil de hacer este cálculo, nosotros lo usamos.
Ahora borra la clase CalculadorSiguienteNumeroParaAnyo de tu proyecto, y modifica la propiedad numero de DocumentoComercial para que no la use. Mira el siguiente código:
Nota que, además de quitar @DefaultValueCalculator, hemos añadido la anotación @ReadOnly. Esto significa que el usuario no puede introducir ni modificar este número. Esta es la forma correcta de hacerlo ahora dado que el número es generado al grabar el objeto, por lo que el valor que tecleara el usuario sería sobrescrito siempre.
Prueba ahora el módulo de Factura o Pedido, verás como el número está vacío y no es editable, y cuando grabes el documento, el número se calcula y se actualiza en la interfaz de usuario.
Sincronizar propiedades persistentes y calculadas
La forma en que calculamos el IVA, el importe base y el importe total es natural y práctica. Usamos propiedades calculadas que calculan, usando Java puro, los valores cada vez que son llamadas.Pero, las propiedades calculadas tienen algunos inconvenientes. Por ejemplo, si quieres hacer un proceso masivo o un informe de todas las facturas cuyo importe total esté entre ciertos rangos. En estos casos, si tienes una base de datos demasiado grande el proceso puede ser lentísimo, porque has de instanciar todas las facturas para calcular su importe total. Una solución para este problema es tener una propiedad persistente, por tanto una columna en la base de datos para el importe de la factura o pedido; así el rendimiento es bastante mayor.
En nuestro caso mantendremos nuestra actuales propiedades calculadas, pero vamos a añadir una nueva, llamada importe, que contendrá el mismo valor que importeTotal, pero importe será persistente con su correspondiente columna en la base de datos. Lo complicado aquí es mantener sincronizado el valor de la propiedad importe. Vamos a usar métodos de retrollamada JPA (y un truco más) en DocumentoComercial para conseguirlo.
El primer paso es añadir la propiedad importe a DocumentoComercial. Nada más fácil, puedes verlo en el siguiente código:
Cuando el usuario añade, modifica o elimina un detalle en la interface de usuario, el iva, importe base e importe total son recalculados con datos frescos instantáneamente, no obstante, para persistir estos cambios el usuario debe Guardar el DocumentoComercial. Para sincronizar importe con importeTotal la primera vez que registramos un documento comercial, nosotros ya sabemos que debemos usar @PrePersist, pero, resulta que JPA no permite marcar más de un método con la misma anotación, por lo tanto, vamos a reordenar nuestro código. Veamos:
Básicamente, llamamos a recalcularImporte() cada vez que una entidad DocumentoComercial es registrada por primera vez en la base de datos. Pero, recalcularImporte() también debe ser ejecutado en la actualización de detalles. Una primera aproximación puede ser marcar recalcularImporte con @PreUpdate, pero este sería ejecutado solo cuando cambian las propiedades de DocumentoComercial, nunca cuando cambian detalles. Nosotros superaremos esto, ejecutando recalcularImporte() siempre que el usuario grabe un DocumentoComercial. Veamos el siguiente código:
La propiedad version asegura que la retrollamada @PreUpdate sea ejecutada siempre que el usuario Salve un DocumentoComercial, porque esta propiedad siempre será actualizada al guardar.
Puedes probar los módulos Factura y Pedido con este código y verás que cuando una línea de detalle se añade, remueve o modifica, la columna importe en la base de datos es correctamente actualizada después de grabar, lista para ser usada en un proceso masivo.
Elimina la tabla DOCUMENTOCOMERCIAL para que se vuelva a generar incluyendo la columna "version".
Lógica desde la base de datos (@Formula)
Idealmente escribirás toda tu lógica de negocio en Java, dentro de tus entidades. Sin embargo, hay ocasiones que esto no es lo más conveniente. Imagina que tienes una propiedad calculada en DocumentoComercial, digamos beneficioEstimado, como la siguiente:Si necesitas realizar un proceso con todas las facturas con un beneficioEstimado mayor de 1000, has de escribir algo parecido al siguiente código:
No puedes usar una condición en la consulta para discriminar por beneficioEstimado, porque beneficioEstimado no está en la base de datos, solo está en el objeto Java, por tanto tienes que instanciar cada objeto para preguntar por su beneficioEstimado A veces esto es una buena opción, pero si tienes una cantidad inmensa de facturas, y solo unas cuantas tienen el beneficioEstimado mayor de 1000, entonces el proceso será muy ineficiente. ¿Qué alternativa tenemos?
Nuestra alternativa es usar la anotación @Formula. @Formula es una extensión de Hibernate al JPA estándar, que te permite mapear tu propiedad contra un estamento SQL. Puedes definir beneficioEstimado con @Formula como muestra el siguiente código:
Esto indica que cuando un DocumentoComercial se lea de la base de datos, el campo beneficioEstimado se rellenará con el cálculo de @Formula, un cálculo que por cierto hace la base de datos. Lo más útil de las propiedades @Formula es que puedes usarlas en las condiciones, por tanto puedes reescribir el anterior proceso como muestra el siguiente código:
De esta forma pones el peso del cálculo de beneficioEstimado y la selección de los registros en el servidor de base de datos, y no el servidor Java.
Este hecho también tiene efecto en modo lista, porque el usuario no puede filtrar ni ordenar por propiedades calculadas, pero sí por propiedades con @Formula:
@Formula es una buena opción para mejorar el rendimiento en algunos casos. De todas formas, generalmente es mejor usar propiedades calculadas y escribir así tu lógica en Java. La ventaja de las propiedades calculadas sobre @Formula es que tu código no es dependiente de la base de datos. Además, con las propiedades calculadas puedes reejecutar el cálculo sin tener que leer el objeto de la base de datos, por tanto puedes usar @Depends.
Pruebas JUnit
Antes de ir a la siguiente lección, vamos a escribir el código JUnit para ésta. Recuerda, el código no está terminado si no tiene pruebas JUnit. Puedes escribir las pruebas antes, durante o después del código principal. Pero siempre has de escribirlas.El código de prueba mostrado aquí no es solo para darte un buen ejemplo, sino también para enseñarte maneras de probar diferentes casos en tu aplicación OpenXava.
Modificar la prueba existente
Crear una nueva prueba para cada nuevo caso parece una buena idea desde un punto de vista estructural, pero en la mayoría de los casos no es práctico, porque de esa forma tu código de prueba crecerá muy rápido, y con el tiempo, ejecutar todas las pruebas supondrá muchísimo tiempo.El enfoque más pragmático es modificar el código de prueba existente para cubrir todos los nuevos casos que hemos desarrollado. Hagámoslo de esta forma.
En nuestro caso, todo el código de esta lección aplica a DocumentoComercial, por tanto vamos a modificar el método testCrear() de PruebaDocumentoComercial para ajustarlo a la nueva funcionalidad. Dejamos el método testCrear() tal como muestra el siguiente código:
Como ves, añadimos una nueva línea, después de login(...), para calcular el siguiente número de documento, y una llamada al nuevo método verificarImporteYBeneficioEstimado().
Ahora nos conviene más calcular el siguiente número de documento al principio para usarlo en el resto de la prueba. Para hacer esto, cambia el viejo método getNumero() por los dos métodos mostrados en el siguiente código:
Anteriormente, teníamos solo getNumero() que calculaba y devolvía el número, ahora tenemos un método para calcular (calcularNumero()), y otro para devolver el resultado (getNumero()). Puedes notar que la lógica del cálculo tiene un pequeño cambio, en vez de usar “DocumentoComercial” como fuente de la consulta usamos "modelo", una variable. Esto es así porque ahora la numeración para facturas y pedidos está separada. Llenamos esta variable, un campo de la clase de prueba, en el constructor, tal como muestra el siguiente código:
En este caso el nombre de módulo, "Factura" o "Pedido", coincide con el nombre de modelo, Factura o Pedido, así que la forma más fácil de obtener el nombre de modelo es desde el nombre de módulo.
Veamos el código que prueba la nueva funcionalidad.
Verificar valores por defecto y propiedades calculadas
En esta lección hemos hecho algunas modificaciones en los valores por defecto. Primero, el valor por defecto para numero ya no se calcula mediante un @DefaultValueCalculator en su lugar usamos un método de retrollamada JPA. Segundo, tenemos una nueva propiedad, porcentajeIVA, cuyo valor inicial se calcula leyendo de un archivo de propiedades. Para probar estos casos hemos de modificar el método verificarValoresDefecto() como ves en el siguiente código:Comprobamos el cálculo del valor por defecto de procentajeIVA y verificamos que numero no tiene valor inicial, porque ahora numero no se calcula hasta el momento de grabar el documento (sección Cálculo de valor por defecto multiusuario). Cuando el documento (factura o pedido) se grabe verificaremos que numero se calcula. Cuando la línea se añade podemos verificar el cálculo de importe de detalle (la propiedad calculada simple, sección Propiedad calculada simple), el valor por defecto para precioPorUnidad (@DefaultValueCalculator, sección Usar @DefaultValueCalculator) y las propiedades de importes del documento (propiedades calculadas que dependen de una colección, sección Propiedades calculadas dependientes de una colección). Probamos todo esto haciendo unas ligeras modificaciones en el ya existente método anyadirDetalles():
Como ves, con estas modificaciones sencillas probamos la mayoría de nuestro nuevo código. Nos quedan sólo las propiedades importe y beneficioEstimado. Las cuales probaremos en la siguiente sección.
Sincronización entre propiedad persistente y calculada / @Formula
En la sección Sincronizar propiedades persistentes y calculadas usamos métodos de retrollamada de JPA en DocumentoComercial para tener una propiedad persistente, importe, sincronizada con una calculada, importeTotal. La propiedad importe solo se muestra en modo lista.En la sección Lógica desde la base de datos hemos creado una propiedad que usa @Formula, beneficioEstimado. Esta propiedad se muestra solo en modo lista.
Obviamente, la forma más simple de probarlo es yendo a modo lista y verificando que los valores para estas dos propiedades son los esperados. En testCrear() llamamos a verificarImporteYBeneficioEstimado(). Veamos su código:
Dado que ahora vamos a modo lista y después volvemos a detalles, hemos de hacer una pequeña modificación en el método verificarCreado(), que es ejecutado justo después de verificarImporteYBeneficioEstimado(). Veamos la modificación:
Quitamos estas líneas porque ahora no es necesario buscar el documento recién creado. Ahora en el método verificarImporteYBeneficioEstimado() vamos a modo lista y escogemos el documento, por tanto ya estamos editando el documento.
¡Enhorabuena! Ahora tus pruebas ya están sincronizadas con tu código. Es un buen momento para ejecutar todas las pruebas de tu aplicación.
Resumen
En esta lección has aprendido algunas formas comunes de añadir lógica de negocio a tus entidades. No hay duda sobre la utilidad de las propiedades calculadas, los métodos de retrollamada o @Formula. Sin embargo, todavía tenemos muchas otras formas de añadir lógica a tu aplicación OpenXava, que vamos a aprender a usar.En futuros lecciones verás como añadir validación, modificar el funcionamiento estándar del módulo y añadir tu propia lógica de negocio, entre otras formas de añadir lógica personalizada a tu aplicación.
Descargar código fuente de esta lección
¿Problemas con la lección? Pregunta en el foro ¿Ha ido bien? Ve a la lección 6